/*
** fmodsound.cpp
** System interface for sound; uses FMOD Ex.
**
**---------------------------------------------------------------------------
** Copyright 1998-2009 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

// HEADER FILES ------------------------------------------------------------

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
extern HWND Window;
#define USE_WINDOWS_DWORD
#else
#define FALSE 0
#define TRUE 1
#endif
// [BB] FreeBSD doesn't accept malloc.h anymore.
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <stdlib.h>
#elif __sun
#include <alloca.h>
#else
#include <malloc.h>
#endif

#include "templates.h"
#ifndef NO_SOUND
#include "fmodsound.h"
#endif
#include "c_cvars.h"
#include "i_system.h"
#include "i_music.h"
#include "v_text.h"
#include "v_video.h"
#include "v_palette.h"
#include "cmdlib.h"
#include "s_sound.h"

// MACROS ------------------------------------------------------------------

// killough 2/21/98: optionally use varying pitched sounds
#define PITCH(freq,pitch) (snd_pitched ? ((freq)*(pitch))/128.f : float(freq))

// Just some extra for music and whatever
#define NUM_EXTRA_SOFTWARE_CHANNELS		1

#define MAX_CHANNELS				256

#define SPECTRUM_SIZE				256

// TYPES -------------------------------------------------------------------

struct FEnumList
{
	const char *Name;
	int Value;
};

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

#ifndef NO_SOUND
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static int Enum_NumForName(const FEnumList *list, const char *name);
static const char *Enum_NameForNum(const FEnumList *list, int num);

#endif
// EXTERNAL DATA DECLARATIONS ----------------------------------------------

EXTERN_CVAR (String, snd_output)
EXTERN_CVAR (Float, snd_sfxvolume)
EXTERN_CVAR (Float, snd_musicvolume)
EXTERN_CVAR (Int, snd_buffersize)
EXTERN_CVAR (Int, snd_samplerate)
EXTERN_CVAR (Bool, snd_pitched)
EXTERN_CVAR (Int, snd_channels)

extern int sfx_empty;

// PUBLIC DATA DEFINITIONS -------------------------------------------------

ReverbContainer *ForcedEnvironment;

CVAR (Int, snd_driver, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Int, snd_buffercount, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, snd_hrtf, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, snd_waterreverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (String, snd_resampler, "Linear", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (String, snd_speakermode, "Auto", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (String, snd_output_format, "PCM-16", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, snd_profile, false, 0)
CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG);

// Underwater low-pass filter cutoff frequency. Set to 0 to disable the filter.
CUSTOM_CVAR (Float, snd_waterlp, 250, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{
	// Clamp to the DSP unit's limits.
	if (*self < 10 && *self != 0)
	{
		self = 10;
	}
	else if (*self > 22000)
	{
		self = 22000;
	}
}

#ifndef NO_SOUND

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static const ReverbContainer *PrevEnvironment;
static bool ShowedBanner;

// The rolloff callback is called during FMOD::Sound::play, so we need this
// global variable to contain the sound info during that time for the
// callback.
static FRolloffInfo *GRolloff;
static float GDistScale;

// In the below lists, duplicate entries are for user selection. When
// queried, only the first one for the particular value is shown.
static const FEnumList OutputNames[] =
{
	{ "Auto",					FMOD_OUTPUTTYPE_AUTODETECT },
	{ "Default",				FMOD_OUTPUTTYPE_AUTODETECT },
	{ "No sound",				FMOD_OUTPUTTYPE_NOSOUND },

	// Windows
	{ "DirectSound",			FMOD_OUTPUTTYPE_DSOUND },
	{ "DSound",					FMOD_OUTPUTTYPE_DSOUND },
	{ "Windows Multimedia",		FMOD_OUTPUTTYPE_WINMM },
	{ "WinMM",					FMOD_OUTPUTTYPE_WINMM },
	{ "WaveOut",				FMOD_OUTPUTTYPE_WINMM },
	{ "OpenAL",					FMOD_OUTPUTTYPE_OPENAL },
	{ "WASAPI",					FMOD_OUTPUTTYPE_WASAPI },
	{ "ASIO",					FMOD_OUTPUTTYPE_ASIO },

	// Linux
	{ "OSS",					FMOD_OUTPUTTYPE_OSS },
	{ "ALSA",					FMOD_OUTPUTTYPE_ALSA },
	{ "ESD",					FMOD_OUTPUTTYPE_ESD },
	{ "SDL",					666 },

	// Mac
#if FMOD_VERSION < 0x43000
	// Sound Manager support was removed sometime in the 4.29 line.
	{ "Sound Manager",			FMOD_OUTPUTTYPE_SOUNDMANAGER },
#endif
	{ "Core Audio",				FMOD_OUTPUTTYPE_COREAUDIO },

	{ NULL, 0 }
};

static const FEnumList SpeakerModeNames[] =
{
	{ "Mono",					FMOD_SPEAKERMODE_MONO },
	{ "Stereo",					FMOD_SPEAKERMODE_STEREO },
	{ "Quad",					FMOD_SPEAKERMODE_QUAD },
	{ "Surround",				FMOD_SPEAKERMODE_SURROUND },
	{ "5.1",					FMOD_SPEAKERMODE_5POINT1 },
	{ "7.1",					FMOD_SPEAKERMODE_7POINT1 },
	{ "Prologic",				FMOD_SPEAKERMODE_PROLOGIC },
	{ "1",						FMOD_SPEAKERMODE_MONO },
	{ "2",						FMOD_SPEAKERMODE_STEREO },
	{ "4",						FMOD_SPEAKERMODE_QUAD },
	{ NULL, 0 }
};

static const FEnumList ResamplerNames[] =
{
	{ "No Interpolation",		FMOD_DSP_RESAMPLER_NOINTERP },
	{ "NoInterp",				FMOD_DSP_RESAMPLER_NOINTERP },
	{ "Linear",					FMOD_DSP_RESAMPLER_LINEAR },
	{ "Cubic",					FMOD_DSP_RESAMPLER_CUBIC },
	{ "Spline",					FMOD_DSP_RESAMPLER_SPLINE },
	{ NULL, 0 }
};

static const FEnumList SoundFormatNames[] =
{
	{ "None",					FMOD_SOUND_FORMAT_NONE },
	{ "PCM-8",					FMOD_SOUND_FORMAT_PCM8 },
	{ "PCM-16",					FMOD_SOUND_FORMAT_PCM16 },
	{ "PCM-24",					FMOD_SOUND_FORMAT_PCM24 },
	{ "PCM-32",					FMOD_SOUND_FORMAT_PCM32 },
	{ "PCM-Float",				FMOD_SOUND_FORMAT_PCMFLOAT },
	{ "GCADPCM",				FMOD_SOUND_FORMAT_GCADPCM },
	{ "IMAADPCM",				FMOD_SOUND_FORMAT_IMAADPCM },
	{ "VAG",					FMOD_SOUND_FORMAT_VAG },
	{ "XMA",					FMOD_SOUND_FORMAT_XMA },
	{ "MPEG",					FMOD_SOUND_FORMAT_MPEG },
	{ NULL, 0 }
};

static const char *OpenStateNames[] =
{
	"Ready",
	"Loading",
	"Error",
	"Connecting",
	"Buffering",
	"Seeking",
	"Streaming"
};

// CODE --------------------------------------------------------------------

//==========================================================================
//
// Enum_NumForName
//
// Returns the value of an enum name, or -1 if not found.
//
//==========================================================================

static int Enum_NumForName(const FEnumList *list, const char *name)
{
	while (list->Name != NULL)
	{
		if (stricmp(list->Name, name) == 0)
		{
			return list->Value;
		}
		list++;
	}
	return -1;
}

//==========================================================================
//
// Enum_NameForNum
//
// Returns the name of an enum value. If there is more than one name for a
// value, on the first one in the list is returned. Returns NULL if there
// was no match.
//
//==========================================================================

static const char *Enum_NameForNum(const FEnumList *list, int num)
{
	while (list->Name != NULL)
	{
		if (list->Value == num)
		{
			return list->Name;
		}
		list++;
	}
	return NULL;
}

//==========================================================================
//
// The container for a streaming FMOD::Sound, for playing music.
//
//==========================================================================

class FMODStreamCapsule : public SoundStream
{
public:
	FMODStreamCapsule(FMOD::Sound *stream, FMODSoundRenderer *owner, const char *url)
		: Owner(owner), Stream(NULL), Channel(NULL),
		  UserData(NULL), Callback(NULL), URL(url), Ended(false)
	{
		SetStream(stream);
	}

	FMODStreamCapsule(void *udata, SoundStreamCallback callback, FMODSoundRenderer *owner)
		: Owner(owner), Stream(NULL), Channel(NULL),
		  UserData(udata), Callback(callback), Ended(false)
	{}

	~FMODStreamCapsule()
	{
		if (Channel != NULL)
		{
			Channel->stop();
		}
		if (Stream != NULL)
		{
			Stream->release();
		}
	}

	void SetStream(FMOD::Sound *stream)
	{
		float frequency;

		Stream = stream;

		// As this interface is for music, make it super-high priority.
		if (FMOD_OK == stream->getDefaults(&frequency, NULL, NULL, NULL))
		{
			stream->setDefaults(frequency, 1, 0, 0);
		}
	}

	bool Play(bool looping, float volume)
	{
		FMOD_RESULT result;

		if (URL.IsNotEmpty())
		{ // Net streams cannot be looped, because they cannot be seeked.
			looping = false;
		}
		Stream->setMode((looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) | FMOD_SOFTWARE | FMOD_2D);
		result = Owner->Sys->playSound(FMOD_CHANNEL_FREE, Stream, true, &Channel);
		if (result != FMOD_OK)
		{
			return false;
		}
		Channel->setChannelGroup(Owner->MusicGroup);
		Channel->setSpeakerMix(1, 1, 1, 1, 1, 1, 1, 1);
		Channel->setVolume(volume);
		// Ensure reverb is disabled.
		FMOD_REVERB_CHANNELPROPERTIES reverb = { 0, };
		if (FMOD_OK == Channel->getReverbProperties(&reverb))
		{
			reverb.Room = -10000;
			Channel->setReverbProperties(&reverb);
		}
		Channel->setPaused(false);
		Ended = false;
		JustStarted = true;
		Starved = false;
		Loop = looping;
		Volume = volume;
		return true;
	}

	void Stop()
	{
		if (Channel != NULL)
		{
			Channel->stop();
			Channel = NULL;
		}
	}

	bool SetPaused(bool paused)
	{
		if (Channel != NULL)
		{
			return FMOD_OK == Channel->setPaused(paused);
		}
		return false;
	}

	unsigned int GetPosition()
	{
		unsigned int pos;

		if (Channel != NULL && FMOD_OK == Channel->getPosition(&pos, FMOD_TIMEUNIT_MS))
		{
			return pos;
		}
		return 0;
	}

	bool IsEnded()
	{
		bool is;
		FMOD_OPENSTATE openstate = FMOD_OPENSTATE_MAX;
		bool starving;

		if (Stream == NULL)
		{
			return true;
		}
		if (FMOD_OK != Stream->getOpenState(&openstate, NULL, &starving))
		{
			openstate = FMOD_OPENSTATE_ERROR;
		}
		if (openstate == FMOD_OPENSTATE_ERROR)
		{
			if (Channel != NULL)
			{
				Channel->stop();
				Channel = NULL;
			}
			return true;
		}
		if (Channel != NULL && (FMOD_OK != Channel->isPlaying(&is) || is == false))
		{
			return true;
		}
		if (Ended)
		{
			Channel->stop();
			Channel = NULL;
			return true;
		}
		if (URL.IsNotEmpty() && !JustStarted && openstate == FMOD_OPENSTATE_READY)
		{
			// Reconnect the stream, since it seems to have stalled.
			// The only way to do this appears to be to completely recreate it.
			FMOD_RESULT result;

			Channel->stop();
			Stream->release();
			Channel = NULL;
			Stream = NULL;
			Owner->Sys->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES);
			// Open the stream asynchronously, so we don't hang the game while trying to reconnect.
			// (It would be nice to do the initial open asynchronously as well, but I'd need to rethink
			// the music system design to pull that off.)
			result = Owner->Sys->createSound(URL, (Loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) | FMOD_SOFTWARE | FMOD_2D |
				FMOD_CREATESTREAM | FMOD_NONBLOCKING, NULL, &Stream);
			JustStarted = true;
			Owner->Sys->setStreamBufferSize(16*1024, FMOD_TIMEUNIT_RAWBYTES);
			return result != FMOD_OK;
		}
		if (JustStarted && openstate == FMOD_OPENSTATE_STREAMING)
		{
			JustStarted = false;
		}
		if (JustStarted && Channel == NULL && openstate == FMOD_OPENSTATE_READY)
		{
			return !Play(Loop, Volume);
		}
		if (starving != Starved)
		{ // Mute the sound if it's starving.
			Channel->setVolume(starving ? 0 : Volume);
			Starved = starving;
		}
		return false;
	}

	void SetVolume(float volume)
	{
		if (Channel != NULL && !Starved)
		{
			Channel->setVolume(volume);
		}
		Volume = volume;
	}

	// Sets the position in ms.
	bool SetPosition(unsigned int ms_pos)
	{
		return FMOD_OK == Channel->setPosition(ms_pos, FMOD_TIMEUNIT_MS);
	}

	// Sets the order number for MOD formats.
	bool SetOrder(int order_pos)
	{
		return FMOD_OK == Channel->setPosition(order_pos, FMOD_TIMEUNIT_MODORDER);
	}

	FString GetStats()
	{
		FString stats;
		FMOD_OPENSTATE openstate;
		unsigned int percentbuffered;
		unsigned int position;
		bool starving;
		float volume;
		float frequency;
		bool paused;
		bool isplaying;

		if (FMOD_OK == Stream->getOpenState(&openstate, &percentbuffered, &starving))
		{
			stats = (openstate <= FMOD_OPENSTATE_STREAMING ? OpenStateNames[openstate] : "Unknown state");
			stats.AppendFormat(",%3d%% buffered, %s", percentbuffered, starving ? "Starving" : "Well-fed");
		}
		if (Channel == NULL)
		{
			stats += ", not playing";
		}
		if (Channel != NULL && FMOD_OK == Channel->getPosition(&position, FMOD_TIMEUNIT_MS))
		{
			stats.AppendFormat(", %d", position);
			if (FMOD_OK == Stream->getLength(&position, FMOD_TIMEUNIT_MS))
			{
				stats.AppendFormat("/%d", position);
			}
			stats += " ms";
		}
		if (Channel != NULL && FMOD_OK == Channel->getVolume(&volume))
		{
			stats.AppendFormat(", %d%%", int(volume * 100));
		}
		if (Channel != NULL && FMOD_OK == Channel->getPaused(&paused) && paused)
		{
			stats += ", paused";
		}
		if (Channel != NULL && FMOD_OK == Channel->isPlaying(&isplaying) && isplaying)
		{
			stats += ", playing";
		}
		if (Channel != NULL && FMOD_OK == Channel->getFrequency(&frequency))
		{
			stats.AppendFormat(", %g Hz", frequency);
		}
		if (JustStarted)
		{
			stats += " JS";
		}
		if (Ended)
		{
			stats += " XX";
		}
		return stats;
	}

	static FMOD_RESULT F_CALLBACK PCMReadCallback(FMOD_SOUND *sound, void *data, unsigned int datalen)
	{
		FMOD_RESULT result;
		FMODStreamCapsule *self;
		
		result = ((FMOD::Sound *)sound)->getUserData((void **)&self);
		if (result != FMOD_OK || self == NULL || self->Callback == NULL || self->Ended)
		{
			// Contrary to the docs, this return value is completely ignored.
			return FMOD_OK;
		}
		if (!self->Callback(self, data, datalen, self->UserData))
		{
			self->Ended = true;
		}
		return FMOD_OK;
	}

	static FMOD_RESULT F_CALLBACK PCMSetPosCallback(FMOD_SOUND *sound, int subsound, unsigned int position, FMOD_TIMEUNIT postype)
	{
		// This is useful if the user calls Channel::setPosition and you want
		// to seek your data accordingly.
		return FMOD_OK;
	}

private:
	FMODSoundRenderer *Owner;
	FMOD::Sound *Stream;
	FMOD::Channel *Channel;
	void *UserData;
	SoundStreamCallback Callback;
	FString URL;
	bool Ended;
	bool JustStarted;
	bool Starved;
	bool Loop;
	float Volume;
};

//==========================================================================
//
// The interface the game uses to talk to FMOD.
//
//==========================================================================

FMODSoundRenderer::FMODSoundRenderer()
{
	InitSuccess = Init();
}

FMODSoundRenderer::~FMODSoundRenderer()
{
	Shutdown();
}

bool FMODSoundRenderer::IsValid()
{
	return InitSuccess;
}

#ifdef _MSC_VER
//==========================================================================
//
// CheckException
//
//==========================================================================

#define FACILITY_VISUALCPP  ((LONG)0x6d)
#define VcppException(sev,err)  ((sev) | (FACILITY_VISUALCPP<<16) | err)

static int CheckException(DWORD code)
{
	if (code == VcppException(ERROR_SEVERITY_ERROR,ERROR_MOD_NOT_FOUND) ||
		code == VcppException(ERROR_SEVERITY_ERROR,ERROR_PROC_NOT_FOUND))
	{
		return EXCEPTION_EXECUTE_HANDLER;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

#endif

//==========================================================================
//
// FMODSoundRenderer :: Init
//
//==========================================================================

bool FMODSoundRenderer::Init()
{
	FMOD_RESULT result;
	unsigned int version;
	FMOD_SPEAKERMODE speakermode;
	FMOD_SOUND_FORMAT format;
	FMOD_DSP_RESAMPLER resampler;
	FMOD_INITFLAGS initflags;
	int samplerate;
	int driver;

	int eval;

	SFXPaused = 0;
	DSPLocked = false;
	MusicGroup = NULL;
	SfxGroup = NULL;
	PausableSfx = NULL;
	SfxConnection = NULL;
	WaterLP = NULL;
	WaterReverb = NULL;
	PrevEnvironment = DefaultEnvironments[0];
	DSPClock.AsOne = 0;
	ChannelGroupTargetUnit = NULL;
	SfxReverbHooked = false;
	SfxReverbPlaceholder = NULL;
	OutputPlugin = 0;

	Printf("I_InitSound: Initializing FMOD\n");

	// Create a System object and initialize.
#ifdef _MSC_VER
	__try {
#endif
	result = FMOD::System_Create(&Sys);
#ifdef _MSC_VER
	}
	__except(CheckException(GetExceptionCode()))
	{
		Sys = NULL;
		Printf(TEXTCOLOR_ORANGE"Failed to load fmodex"
#ifdef _WIN64
			"64"
#endif
			".dll\n");
		return false;
	}
#endif
	if (result != FMOD_OK)
	{
		Sys = NULL;
		Printf(TEXTCOLOR_ORANGE"Failed to create FMOD system object: Error %d\n", result);
		return false;
	}

	result = Sys->getVersion(&version);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_ORANGE"Could not validate FMOD version: Error %d\n", result);
		return false;
	}

	const char *wrongver = NULL;
	if (version < 0x42000)
	{
		wrongver = "an old";
	}
#if FMOD_VERSION < 0x42700
	else if ((version & 0xFFFF00) > 0x42600)
#else
	else if ((version & 0xFFFF00) > (FMOD_VERSION & 0xFFFF00))
#endif
	{
		wrongver = "a new";
	}
	if (wrongver != NULL)
	{
		Printf (" "TEXTCOLOR_ORANGE"Error! You are using %s version of FMOD (%x.%02x.%02x).\n"
				" "TEXTCOLOR_ORANGE"This program was built for version %x.%02x.%02x\n",
				wrongver,
				version >> 16, (version >> 8) & 255, version & 255,
				FMOD_VERSION >> 16, (FMOD_VERSION >> 8) & 255, FMOD_VERSION & 255);
		return false;
	}
	ActiveFMODVersion = version;

	if (!ShowedBanner)
	{
		// '\xa9' is the copyright symbol in the Windows-1252 code page.
		Printf("FMOD Sound System, copyright \xa9 Firelight Technologies Pty, Ltd., 1994-2009.\n");
		Printf("Loaded FMOD version %x.%02x.%02x\n", version >> 16, (version >> 8) & 255, version & 255);
		ShowedBanner = true;
	}
#ifdef _WIN32
	if (OSPlatform == os_WinNT4)
	{
		// The following was true as of FMOD 3. I don't know if it still
		// applies to FMOD Ex, nor do I have an NT 4 install anymore, but
		// there's no reason to get rid of it yet.
		//
		// If running Windows NT 4, we need to initialize DirectSound before
		// using WinMM. If we don't, then FSOUND_Close will corrupt a
		// heap. This might just be the Audigy's drivers--I don't know why
		// it happens. At least the fix is simple enough. I only need to
		// initialize DirectSound once, and then I can initialize/close
		// WinMM as many times as I want.
		//
		// Yes, using WinMM under NT 4 is a good idea. I can get latencies as
		// low as 20 ms with WinMM, but with DirectSound I need to have the
		// latency as high as 120 ms to avoid crackling--quite the opposite
		// from the other Windows versions with real DirectSound support.

		static bool inited_dsound = false;

		if (!inited_dsound)
		{
			if (Sys->setOutput(FMOD_OUTPUTTYPE_DSOUND) == FMOD_OK)
			{
				if (Sys->init(1, FMOD_INIT_NORMAL, 0) == FMOD_OK)
				{
					inited_dsound = true;
					Sleep(50);
					Sys->close();
				}
				Sys->setOutput(FMOD_OUTPUTTYPE_WINMM);
			}
		}
	}
#endif

#ifndef _WIN32
	// Try to load SDL output plugin
	result = Sys->setPluginPath(progdir);	// Should we really look for it in the program directory?
	result = Sys->loadPlugin("liboutput_sdl.so", &OutputPlugin);
	if (result != FMOD_OK)
	{
		OutputPlugin = 0;
	}
#endif

	// Set the user specified output mode.
	eval = Enum_NumForName(OutputNames, snd_output);
	if (eval >= 0)
	{
		if (eval == 666 && OutputPlugin != 0)
		{
			result = Sys->setOutputByPlugin(OutputPlugin);
		}
		else
		{
			result = Sys->setOutput(FMOD_OUTPUTTYPE(eval));
		}
		if (result != FMOD_OK)
		{
			Printf(TEXTCOLOR_BLUE"Setting output type '%s' failed. Using default instead. (Error %d)\n", *snd_output, result);
			eval = FMOD_OUTPUTTYPE_AUTODETECT;
			Sys->setOutput(FMOD_OUTPUTTYPE_AUTODETECT);
		}
	}
	
	result = Sys->getNumDrivers(&driver);
#ifdef unix
	if (result == FMOD_OK)
	{
		// On Linux, FMOD defaults to OSS. If OSS is not present, it doesn't
		// try ALSA; it just fails. We'll try for it, but only if OSS wasn't
		// explicitly specified for snd_output.
		if (driver == 0 && eval == FMOD_OUTPUTTYPE_AUTODETECT)
		{
			FMOD_OUTPUTTYPE output;
			if (FMOD_OK == Sys->getOutput(&output))
			{
				if (output == FMOD_OUTPUTTYPE_OSS)
				{
					Printf(TEXTCOLOR_BLUE"OSS could not be initialized. Trying ALSA.\n");
					Sys->setOutput(FMOD_OUTPUTTYPE_ALSA);
					result = Sys->getNumDrivers(&driver);
				}
			}
		}
	}
#endif
	if (result == FMOD_OK)
	{
		if (driver == 0)
		{
			Printf(TEXTCOLOR_ORANGE"No working sound devices found. Try a different snd_output?\n");
			return false;
		}
		if (snd_driver >= driver)
		{
			Printf(TEXTCOLOR_BLUE"Driver %d does not exist. Using 0.\n", *snd_driver);
			driver = 0;
		}
		else
		{
			driver = snd_driver;
		}
		result = Sys->setDriver(driver);
	}
	result = Sys->getDriver(&driver);
	result = Sys->getDriverCaps(driver, &Driver_Caps, &Driver_MinFrequency, &Driver_MaxFrequency, &speakermode);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_BLUE"Could not ascertain driver capabilities. Some things may be weird. (Error %d)\n", result);
		// Fill in some default to pretend it worked. (But as long as we specify a valid driver,
		// can this call actually fail?)
		Driver_Caps = 0;
		Driver_MinFrequency = 4000;
		Driver_MaxFrequency = 48000;
		speakermode = FMOD_SPEAKERMODE_STEREO;
	}

	// Set the user selected speaker mode.
	eval = Enum_NumForName(SpeakerModeNames, snd_speakermode);
	if (eval >= 0)
	{
		speakermode = FMOD_SPEAKERMODE(eval);
	}
	result = Sys->setSpeakerMode(speakermode);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_BLUE"Could not set speaker mode to '%s'. (Error %d)\n", *snd_speakermode, result);
	}

	// Set software format
	eval = Enum_NumForName(SoundFormatNames, snd_output_format);
	format = eval >= 0 ? FMOD_SOUND_FORMAT(eval) : FMOD_SOUND_FORMAT_PCM16;
	eval = Enum_NumForName(ResamplerNames, snd_resampler);
	resampler = eval >= 0 ? FMOD_DSP_RESAMPLER(eval) : FMOD_DSP_RESAMPLER_LINEAR;
	samplerate = clamp<int>(snd_samplerate, Driver_MinFrequency, Driver_MaxFrequency);
	if (samplerate == 0 || snd_samplerate == 0)
	{ // Creative's ASIO drivers report the only supported frequency as 0!
		if (FMOD_OK != Sys->getSoftwareFormat(&samplerate, NULL, NULL, NULL, NULL, NULL))
		{
			samplerate = 48000;
		}
	}
	if (samplerate != snd_samplerate && snd_samplerate != 0)
	{
		Printf(TEXTCOLOR_BLUE"Sample rate %d is unsupported. Trying %d.\n", *snd_samplerate, samplerate);
	}
	result = Sys->setSoftwareFormat(samplerate, format, 0, 0, resampler);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_BLUE"Could not set mixing format. Defaults will be used. (Error %d)\n", result);
	}

	// Set software channels according to snd_channels
	result = Sys->setSoftwareChannels(snd_channels + NUM_EXTRA_SOFTWARE_CHANNELS);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_BLUE"Failed to set the preferred number of channels. (Error %d)\n", result);
	}

	if (Driver_Caps & FMOD_CAPS_HARDWARE_EMULATED)
	{ // The user has the 'Acceleration' slider set to off!
	  // This is really bad for latency!
		Printf (TEXTCOLOR_BLUE"Warning: The sound acceleration slider has been set to off.\n");
		Printf (TEXTCOLOR_BLUE"Please turn it back on if you want decent sound.\n");
		result = Sys->setDSPBufferSize(1024, 10);	// At 48khz, the latency between issuing an fmod command and hearing it will now be about 213ms.
	}
	else if (snd_buffersize != 0 || snd_buffercount != 0)
	{
		int buffersize = snd_buffersize ? snd_buffersize : 1024;
		int buffercount = snd_buffercount ? snd_buffercount : 4;
		result = Sys->setDSPBufferSize(buffersize, buffercount);
	}
	/*
	// [BC] Initalize FMOD with global focus, so that sound continues to play even if
	// Skulltag is minimized. That way, people can join a server, alt+tab out while
	// waiting for someone to show up, and then hear if someone joins.
	if (!FModLog (FSOUND_Init (snd_samplerate, 64, FSOUND_INIT_DSOUND_DEFERRED|FSOUND_INIT_GLOBALFOCUS)))
	*/
	else
	{
		result = FMOD_OK;
	}
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_BLUE"Setting DSP buffer size failed. (Error %d)\n", result);
	}
	// Try to init
	initflags = FMOD_INIT_NORMAL;
	if (snd_hrtf)
	{
		initflags |= FMOD_INIT_SOFTWARE_HRTF;
	}
	if (snd_profile)
	{
		initflags |= FMOD_INIT_ENABLE_PROFILE;
	}
	for (;;)
	{
		result = Sys->init(MAX(*snd_channels, MAX_CHANNELS), initflags, 0);
		if (result == FMOD_ERR_OUTPUT_CREATEBUFFER)
		{ 
			// Possible causes of a buffer creation failure:
			// 1. The speaker mode selected isn't supported by this soundcard. Force it to stereo.
			// 2. The output format is unsupported. Force it to 16-bit PCM.
			// 3. ???
			result = Sys->getSpeakerMode(&speakermode);
			if (result == FMOD_OK &&
				speakermode != FMOD_SPEAKERMODE_STEREO &&
				FMOD_OK == Sys->setSpeakerMode(FMOD_SPEAKERMODE_STEREO))
			{
				Printf(TEXTCOLOR_RED"  Buffer creation failed. Retrying with stereo output.\n");
				continue;
			}
			result = Sys->getSoftwareFormat(&samplerate, &format, NULL, NULL, &resampler, NULL);
			if (result == FMOD_OK &&
				format != FMOD_SOUND_FORMAT_PCM16 &&
				FMOD_OK == Sys->setSoftwareFormat(samplerate, FMOD_SOUND_FORMAT_PCM16, 0, 0, resampler))
			{
				Printf(TEXTCOLOR_RED"  Buffer creation failed. Retrying with PCM-16 output.\n");
				continue;
			}
		}
#ifdef _WIN32
		else if (result == FMOD_ERR_OUTPUT_INIT)
		{
			FMOD_OUTPUTTYPE output;
			result = Sys->getOutput(&output);
			if (result == FMOD_OK && output != FMOD_OUTPUTTYPE_DSOUND)
			{
				Printf(TEXTCOLOR_BLUE"  Init failed for output type %s. Retrying with DirectSound.\n",
					Enum_NameForNum(OutputNames, output));
				if (FMOD_OK == Sys->setOutput(FMOD_OUTPUTTYPE_DSOUND))
				{
					continue;
				}
			}
		}
#endif
		break;
	}
	if (result != FMOD_OK)
	{ // Initializing FMOD failed. Cry cry.
		Printf(TEXTCOLOR_ORANGE"  System::init returned error code %d\n", result);
		return false;
	}

	// Create channel groups
	result = Sys->createChannelGroup("Music", &MusicGroup);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_ORANGE"  Could not create music channel group. (Error %d)\n", result);
		return false;
	}

	result = Sys->createChannelGroup("SFX", &SfxGroup);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_ORANGE"  Could not create sfx channel group. (Error %d)\n", result);
		return false;
	}

	result = Sys->createChannelGroup("Pausable SFX", &PausableSfx);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_ORANGE"  Could not create pausable sfx channel group. (Error %d)\n", result);
		return false;
	}

	result = SfxGroup->addGroup(PausableSfx);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_BLUE"  Could not attach pausable sfx to sfx channel group. (Error %d)\n", result);
	}

	// Create DSP units for underwater effect
	result = Sys->createDSPByType(FMOD_DSP_TYPE_LOWPASS, &WaterLP);
	if (result != FMOD_OK)
	{
		Printf(TEXTCOLOR_BLUE"  Could not create underwater lowpass unit. (Error %d)\n", result);
	}
	else
	{
		result = Sys->createDSPByType(FMOD_DSP_TYPE_REVERB, &WaterReverb);
		if (result != FMOD_OK)
		{
			Printf(TEXTCOLOR_BLUE"  Could not create underwater reverb unit. (Error %d)\n", result);
		}
	}

	// Connect underwater DSP unit between PausableSFX and SFX groups, while
	// retaining the connection established by SfxGroup->addGroup().
	if (WaterLP != NULL)
	{
		FMOD::DSP *sfx_head, *pausable_head;

		result = SfxGroup->getDSPHead(&sfx_head);
		if (result == FMOD_OK)
		{
			result = sfx_head->getInput(0, &pausable_head, &SfxConnection);
			if (result == FMOD_OK)
			{
				// The placeholder mixer is for reference to where to connect the SFX
				// reverb unit once it gets created.
				result = Sys->createDSPByType(FMOD_DSP_TYPE_MIXER, &SfxReverbPlaceholder);
				if (result == FMOD_OK)
				{
					// Replace the PausableSFX->SFX connection with
					// PausableSFX->ReverbPlaceholder->SFX.
					result = SfxReverbPlaceholder->addInput(pausable_head, NULL);
					if (result == FMOD_OK)
					{
						FMOD::DSPConnection *connection;
						result = sfx_head->addInput(SfxReverbPlaceholder, &connection);
						if (result == FMOD_OK)
						{
							sfx_head->disconnectFrom(pausable_head);
							SfxReverbPlaceholder->setActive(true);
							SfxReverbPlaceholder->setBypass(true);
							// The placeholder now takes the place of the pausable_head
							// for the following connections.
							pausable_head = SfxReverbPlaceholder;
							SfxConnection = connection;
						}
					}
					else
					{
						SfxReverbPlaceholder->release();
						SfxReverbPlaceholder = NULL;
					}
				}
				result = WaterLP->addInput(pausable_head, NULL);
				WaterLP->setActive(false);
				WaterLP->setParameter(FMOD_DSP_LOWPASS_CUTOFF, snd_waterlp);
				WaterLP->setParameter(FMOD_DSP_LOWPASS_RESONANCE, 2);
				if (WaterReverb != NULL)
				{
					FMOD::DSPConnection *dry;
					result = WaterReverb->addInput(pausable_head, &dry);
					if (result == FMOD_OK)
					{
						result = dry->setMix(0.1f);
						if (result == FMOD_OK)
						{
							result = WaterReverb->addInput(WaterLP, NULL);
							if (result == FMOD_OK)
							{
								result = sfx_head->addInput(WaterReverb, NULL);
								if (result == FMOD_OK)
								{
									WaterReverb->setParameter(FMOD_DSP_REVERB_ROOMSIZE, 0.001f);
									WaterReverb->setParameter(FMOD_DSP_REVERB_DAMP, 0.2f);
									WaterReverb->setActive(false);
								}
							}
						}
					}
				}
				else
				{
					result = sfx_head->addInput(WaterLP, NULL);
				}
			}
		}
	}
	LastWaterLP = snd_waterlp;

	// Find the FMOD Channel Group Target Unit. To completely eliminate sound
	// while the program is deactivated, we can deactivate this DSP unit, and
	// all audio processing will cease. This is not directly exposed by the
	// API but can be easily located by getting the master channel group and
	// tracing its single output, since it is known to hook up directly to the
	// Channel Group Target Unit. (See FMOD Profiler for proof.)
	FMOD::ChannelGroup *master_group;
	result = Sys->getMasterChannelGroup(&master_group);
	if (result == FMOD_OK)
	{
		FMOD::DSP *master_head;

		result = master_group->getDSPHead(&master_head);
		if (result == FMOD_OK)
		{
			result = master_head->getOutput(0, &ChannelGroupTargetUnit, NULL);
			if (result != FMOD_OK)
			{
				ChannelGroupTargetUnit = NULL;
			}
		}
	}

	if (FMOD_OK != Sys->getSoftwareFormat(&OutputRate, NULL, NULL, NULL, NULL, NULL))
	{
		OutputRate = 48000;		// Guess, but this should never happen.
	}
	Sys->set3DSettings(0.5f, 96.f, 1.f);
	Sys->set3DRolloffCallback(RolloffCallback);
	snd_sfxvolume.Callback ();
	return true;
}

//==========================================================================
//
// FMODSoundRenderer :: Shutdown
//
//==========================================================================

void FMODSoundRenderer::Shutdown()
{
	if (Sys != NULL)
	{
		if (MusicGroup != NULL)
		{
			MusicGroup->release();
			MusicGroup = NULL;
		}
		if (PausableSfx != NULL)
		{
			PausableSfx->release();
			PausableSfx = NULL;
		}
		if (SfxGroup != NULL)
		{
			SfxGroup->release();
			SfxGroup = NULL;
		}
		if (WaterLP != NULL)
		{
			WaterLP->release();
			WaterLP = NULL;
		}
		if (WaterReverb != NULL)
		{
			WaterReverb->release();
			WaterReverb = NULL;
		}
		if (SfxReverbPlaceholder != NULL)
		{
			SfxReverbPlaceholder->release();
			SfxReverbPlaceholder = NULL;
		}

		Sys->close();
		if (OutputPlugin != 0)
		{
			Sys->unloadPlugin(OutputPlugin);
			OutputPlugin = 0;
		}
		Sys->release();
		Sys = NULL;
	}
}

//==========================================================================
//
// FMODSoundRenderer :: GetOutputRate
//
//==========================================================================

float FMODSoundRenderer::GetOutputRate()
{
	return (float)OutputRate;
}

//==========================================================================
//
// FMODSoundRenderer :: PrintStatus
//
//==========================================================================

void FMODSoundRenderer::PrintStatus()
{
	FMOD_OUTPUTTYPE output;
	FMOD_SPEAKERMODE speakermode;
	FMOD_SOUND_FORMAT format;
	FMOD_DSP_RESAMPLER resampler;
	int driver;
	int samplerate;
	int numoutputchannels;
	int num2d, num3d, total;
	unsigned int bufferlength;
	int numbuffers;

	Printf ("Loaded FMOD version: "TEXTCOLOR_GREEN"%x.%02x.%02x\n", ActiveFMODVersion >> 16,
		(ActiveFMODVersion >> 8) & 255, ActiveFMODVersion & 255);
	if (FMOD_OK == Sys->getOutput(&output))
	{
		Printf ("Output type: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(OutputNames, output));
	}
	if (FMOD_OK == Sys->getSpeakerMode(&speakermode))
	{
		Printf ("Speaker mode: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(SpeakerModeNames, speakermode));
	}
	if (FMOD_OK == Sys->getDriver(&driver))
	{
		char name[256];
		if (FMOD_OK != Sys->getDriverInfo(driver, name, sizeof(name), NULL))
		{
			strcpy(name, "Unknown");
		}
		Printf ("Driver: "TEXTCOLOR_GREEN"%d"TEXTCOLOR_NORMAL" ("TEXTCOLOR_ORANGE"%s"TEXTCOLOR_NORMAL")\n", driver, name);
		DumpDriverCaps(Driver_Caps, Driver_MinFrequency, Driver_MaxFrequency);
	}
	if (FMOD_OK == Sys->getHardwareChannels(&num2d, &num3d, &total))
	{
		Printf (TEXTCOLOR_YELLOW "Hardware 2D channels: "TEXTCOLOR_GREEN"%d\n", num2d);
		Printf (TEXTCOLOR_YELLOW "Hardware 3D channels: "TEXTCOLOR_GREEN"%d\n", num3d);
		Printf (TEXTCOLOR_YELLOW "Total hardware channels: "TEXTCOLOR_GREEN"%d\n", total);
	}
	if (FMOD_OK == Sys->getSoftwareFormat(&samplerate, &format, &numoutputchannels, NULL, &resampler, NULL))
	{
		Printf (TEXTCOLOR_LIGHTBLUE "Software mixer sample rate: "TEXTCOLOR_GREEN"%d\n", samplerate);
		Printf (TEXTCOLOR_LIGHTBLUE "Software mixer format: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(SoundFormatNames, format));
		Printf (TEXTCOLOR_LIGHTBLUE "Software mixer channels: "TEXTCOLOR_GREEN"%d\n", numoutputchannels);
		Printf (TEXTCOLOR_LIGHTBLUE "Software mixer resampler: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(ResamplerNames, resampler));
	}
	if (FMOD_OK == Sys->getDSPBufferSize(&bufferlength, &numbuffers))
	{
		Printf (TEXTCOLOR_LIGHTBLUE "DSP buffers: "TEXTCOLOR_GREEN"%u samples x %d\n", bufferlength, numbuffers);
	}
}

//==========================================================================
//
// FMODSoundRenderer :: DumpDriverCaps
//
//==========================================================================

void FMODSoundRenderer::DumpDriverCaps(FMOD_CAPS caps, int minfrequency, int maxfrequency)
{
	Printf (TEXTCOLOR_OLIVE "   Min. frequency: "TEXTCOLOR_GREEN"%d\n", minfrequency);
	Printf (TEXTCOLOR_OLIVE "   Max. frequency: "TEXTCOLOR_GREEN"%d\n", maxfrequency);
	Printf ("  Features:\n");
	if (caps == 0)									Printf(TEXTCOLOR_OLIVE "   None\n");
	if (caps & FMOD_CAPS_HARDWARE)					Printf(TEXTCOLOR_OLIVE "   Hardware mixing\n");
	if (caps & FMOD_CAPS_HARDWARE_EMULATED)			Printf(TEXTCOLOR_OLIVE "   Hardware acceleration is turned off!\n");
	if (caps & FMOD_CAPS_OUTPUT_MULTICHANNEL)		Printf(TEXTCOLOR_OLIVE "   Multichannel\n");
	if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM8)		Printf(TEXTCOLOR_OLIVE "   PCM-8");
	if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM16)		Printf(TEXTCOLOR_OLIVE "   PCM-16");
	if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM24)		Printf(TEXTCOLOR_OLIVE "   PCM-24");
	if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM32)		Printf(TEXTCOLOR_OLIVE "   PCM-32");
	if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCMFLOAT)	Printf(TEXTCOLOR_OLIVE "   PCM-Float");
	if (caps & (FMOD_CAPS_OUTPUT_FORMAT_PCM8 | FMOD_CAPS_OUTPUT_FORMAT_PCM16 | FMOD_CAPS_OUTPUT_FORMAT_PCM24 | FMOD_CAPS_OUTPUT_FORMAT_PCM32 | FMOD_CAPS_OUTPUT_FORMAT_PCMFLOAT))
	{
		Printf("\n");
	}
	if (caps & FMOD_CAPS_REVERB_EAX2)				Printf(TEXTCOLOR_OLIVE "   EAX2");
	if (caps & FMOD_CAPS_REVERB_EAX3)				Printf(TEXTCOLOR_OLIVE "   EAX3");
	if (caps & FMOD_CAPS_REVERB_EAX4)				Printf(TEXTCOLOR_OLIVE "   EAX4");
	if (caps & FMOD_CAPS_REVERB_EAX5)				Printf(TEXTCOLOR_OLIVE "   EAX5");
	if (caps & FMOD_CAPS_REVERB_I3DL2)				Printf(TEXTCOLOR_OLIVE "   I3DL2");
	if (caps & (FMOD_CAPS_REVERB_EAX2 | FMOD_CAPS_REVERB_EAX3 | FMOD_CAPS_REVERB_EAX4 | FMOD_CAPS_REVERB_EAX5 | FMOD_CAPS_REVERB_I3DL2))
	{
		Printf("\n");
	}
	if (caps & FMOD_CAPS_REVERB_LIMITED)			Printf("TEXTCOLOR_OLIVE    Limited reverb\n");
}

//==========================================================================
//
// FMODSoundRenderer :: PrintDriversList
//
//==========================================================================

void FMODSoundRenderer::PrintDriversList()
{
	int numdrivers;
	int i;
	char name[256];
	
	if (FMOD_OK == Sys->getNumDrivers(&numdrivers))
	{
		for (i = 0; i < numdrivers; ++i)
		{
			if (FMOD_OK == Sys->getDriverInfo(i, name, sizeof(name), NULL))
			{
				Printf("%d. %s\n", i, name);
			}
		}
	}
}

//==========================================================================
//
// FMODSoundRenderer :: GatherStats
//
//==========================================================================

FString FMODSoundRenderer::GatherStats()
{
	int channels;
	float dsp, stream, update, geometry, total;
	FString out;

	channels = 0;
	total = update = geometry = stream = dsp = 0;
	Sys->getChannelsPlaying(&channels);
#if FMOD_VERSION >= 0x42501
	// We were built with an FMOD with the geometry parameter.
	if (ActiveFMODVersion >= 0x42501)
	{ // And we are running with an FMOD that includes it.
		FMOD_System_GetCPUUsage((FMOD_SYSTEM *)Sys, &dsp, &stream, &geometry, &update, &total);
	}
	else
	{ // And we are running with an FMOD that does not include it.
	  // Cast the function to the appropriate type and call through the cast,
	  // since the headers are for the newer version.
		((FMOD_RESULT (F_API *)(FMOD_SYSTEM *, float *, float *, float *, float *))
			FMOD_System_GetCPUUsage)((FMOD_SYSTEM *)Sys, &dsp, &stream, &update, &total);
	}
#else
	// Same as above, except the headers we used do not include the geometry parameter.
	if (ActiveFMODVersion >= 0x42501)
	{
		((FMOD_RESULT (F_API *)(FMOD_SYSTEM *, float *, float *, float *, float *, float *))
			FMOD_System_GetCPUUsage)((FMOD_SYSTEM *)Sys, &dsp, &stream, &geometry, &update, &total);
	}
	else
	{
		FMOD_System_GetCPUUsage((FMOD_SYSTEM *)Sys, &dsp, &stream, &update, &total);
	}
#endif

	out.Format ("%d channels,"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% CPU "
		"(DSP:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% "
		"Stream:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% "
		"Geometry:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% "
		"Update:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%%)",
		channels, total, dsp, stream, geometry, update);
	return out;
}

//==========================================================================
//
// FMODSoundRenderer :: SetSfxVolume
//
//==========================================================================

void FMODSoundRenderer::SetSfxVolume(float volume)
{
	SfxGroup->setVolume(volume);
}

//==========================================================================
//
// FMODSoundRenderer :: SetMusicVolume
//
//==========================================================================

void FMODSoundRenderer::SetMusicVolume(float volume)
{
	MusicGroup->setVolume(volume);
}

//==========================================================================
//
// FMODSoundRenderer :: CreateStream
//
// Creates a streaming sound that receives PCM data through a callback.
//
//==========================================================================

SoundStream *FMODSoundRenderer::CreateStream (SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata)
{
	FMODStreamCapsule *capsule;
	FMOD::Sound *sound;
	FMOD_RESULT result;
	FMOD_CREATESOUNDEXINFO exinfo;
	FMOD_MODE mode;
	int sample_shift;
	int channel_shift;

	InitCreateSoundExInfo(&exinfo);
	capsule = new FMODStreamCapsule (userdata, callback, this);

	mode = FMOD_2D | FMOD_OPENUSER | FMOD_LOOP_NORMAL | FMOD_SOFTWARE | FMOD_CREATESTREAM | FMOD_OPENONLY;
	sample_shift = (flags & (SoundStream::Bits32 | SoundStream::Float)) ? 2 : (flags & SoundStream::Bits8) ? 0 : 1;
	channel_shift = (flags & SoundStream::Mono) ? 0 : 1;

	// Chunk size of stream update in samples. This will be the amount of data
	// passed to the user callback.
	exinfo.decodebuffersize	 = buffbytes >> (sample_shift + channel_shift);

	// Number of channels in the sound.
	exinfo.numchannels		 = 1 << channel_shift;

	// Length of PCM data in bytes of whole song (for Sound::getLength).
	// This pretends it's extremely long.
	exinfo.length			 = ~0u;

	// Default playback rate of sound. */
	exinfo.defaultfrequency	 = samplerate;

	// Data format of sound.
	if (flags & SoundStream::Float)
	{
		exinfo.format = FMOD_SOUND_FORMAT_PCMFLOAT;
	}
	else if (flags & SoundStream::Bits32)
	{
		exinfo.format = FMOD_SOUND_FORMAT_PCM32;
	}
	else if (flags & SoundStream::Bits8)
	{
		exinfo.format = FMOD_SOUND_FORMAT_PCM8;
	}
	else
	{
		exinfo.format = FMOD_SOUND_FORMAT_PCM16;
	}

	// User callback for reading.
	exinfo.pcmreadcallback	 = FMODStreamCapsule::PCMReadCallback;

	// User callback for seeking.
	exinfo.pcmsetposcallback = FMODStreamCapsule::PCMSetPosCallback;

	// User data to be attached to the sound during creation.  Access via Sound::getUserData.
	exinfo.userdata = capsule;

	result = Sys->createSound(NULL, mode, &exinfo, &sound);
	if (result != FMOD_OK)
	{
		delete capsule;
		return NULL;
	}
	capsule->SetStream(sound);
	return capsule;
}

//==========================================================================
//
// GetTagData
//
// Checks for a string-type tag, and returns its data.
//
//==========================================================================

const char *GetTagData(FMOD::Sound *sound, const char *tag_name)
{
	FMOD_TAG tag;

	if (FMOD_OK == sound->getTag(tag_name, 0, &tag) &&
		(tag.datatype == FMOD_TAGDATATYPE_STRING || tag.datatype == FMOD_TAGDATATYPE_STRING_UTF8))
	{
		return (const char *)tag.data;
	}
	return NULL;
}

//==========================================================================
//
// SetCustomLoopPts
//
// Sets up custom sound loops by checking for these tags:
//    LOOP_START
//    LOOP_END
//    LOOP_BIDI
//
//==========================================================================

static void SetCustomLoopPts(FMOD::Sound *sound)
{
#if 0
	FMOD_TAG tag;
	int numtags;
	if (FMOD_OK == stream->getNumTags(&numtags, NULL))
	{
		for (int i = 0; i < numtags; ++i)
		{
			if (FMOD_OK == sound->getTag(NULL, i, &tag))
			{
				Printf("Tag %2d. %d %s = %s\n", i, tag.datatype, tag.name, tag.data);
			}
		}
	}
#endif
	const char *tag_data;
	unsigned int looppt[2];
	bool looppt_as_samples[2], have_looppt[2] = { false };
	static const char *const loop_tags[2] = { "LOOP_START", "LOOP_END" };

	for (int i = 0; i < 2; ++i)
	{
		if (NULL != (tag_data = GetTagData(sound, loop_tags[i])))
		{
			if (S_ParseTimeTag(tag_data, &looppt_as_samples[i], &looppt[i]))
			{
				have_looppt[i] = true;
			}
			else
			{
				Printf("Invalid %s tag: '%s'\n", loop_tags[i], tag_data);
			}
		}
	}
	if (have_looppt[0] && !have_looppt[1])
	{ // Have a start tag, but not an end tag: End at the end of the song.
		have_looppt[1] = (FMOD_OK == sound->getLength(&looppt[1], FMOD_TIMEUNIT_PCM));
		looppt_as_samples[1] = true;
	}
	else if (!have_looppt[0] && have_looppt[1])
	{ // Have an end tag, but no start tag: Start at beginning of the song.
		looppt[0] = 0;
		looppt_as_samples[0] = true;
		have_looppt[0] = true;
	}
	if (have_looppt[0] && have_looppt[1])
	{ // Have both loop points: Try to set the loop.
		FMOD_RESULT res = sound->setLoopPoints(
			looppt[0], looppt_as_samples[0] ? FMOD_TIMEUNIT_PCM : FMOD_TIMEUNIT_MS,
			looppt[1] - 1, looppt_as_samples[1] ? FMOD_TIMEUNIT_PCM : FMOD_TIMEUNIT_MS);
		if (res != FMOD_OK)
		{
			Printf("Setting custom loop points failed. Error %d\n", res);
		}
	}
	// Check for a bi-directional loop.
	if (NULL != (tag_data = GetTagData(sound, "LOOP_BIDI")) &&
		(stricmp(tag_data, "on") == 0 ||
		 stricmp(tag_data, "true") == 0 ||
		 stricmp(tag_data, "yes") == 0 ||
		 stricmp(tag_data, "1") == 0))
	{
		FMOD_MODE mode;
		if (FMOD_OK == (sound->getMode(&mode)))
		{
			sound->setMode(mode & ~(FMOD_LOOP_OFF | FMOD_LOOP_NORMAL) | FMOD_LOOP_BIDI);
		}
	}
}

//==========================================================================
//
// FMODSoundRenderer :: OpenStream
//
// Creates a streaming sound from a file on disk.
//
//==========================================================================

SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int flags, int offset, int length)
{
	FMOD_MODE mode;
	FMOD_CREATESOUNDEXINFO exinfo;
	FMOD::Sound *stream;
	FMOD_RESULT result;
	bool url;

	InitCreateSoundExInfo(&exinfo);
	mode = FMOD_SOFTWARE | FMOD_2D | FMOD_CREATESTREAM;
	if (flags & SoundStream::Loop)
	{
		mode |= FMOD_LOOP_NORMAL;
	}
	if (offset == -1)
	{
		mode |= FMOD_OPENMEMORY;
		offset = 0;
	}
	exinfo.length = length;
	exinfo.fileoffset = offset;
	if ((*snd_midipatchset)[0] != '\0')
	{
		exinfo.dlsname = snd_midipatchset;
	}

	url = (offset == 0 && length == 0 && strstr(filename_or_data, "://") > filename_or_data);
	if (url)
	{
		// Use a larger buffer for URLs so that it's less likely to be effected
		// by hiccups in the data rate from the remote server.
		Sys->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES);
	}
	result = Sys->createSound(filename_or_data, mode, &exinfo, &stream);
	if (url)
	{
		// Restore standard buffer size.
		Sys->setStreamBufferSize(16*1024, FMOD_TIMEUNIT_RAWBYTES);
	}
	if (result == FMOD_ERR_FORMAT && exinfo.dlsname != NULL)
	{
		// FMOD_ERR_FORMAT could refer to either the main sound file or
		// to the DLS instrument set. Try again without special DLS
		// instruments to see if that lets it succeed.
		exinfo.dlsname = NULL;
		result = Sys->createSound(filename_or_data, mode, &exinfo, &stream);
		if (result == FMOD_OK)
		{
			Printf("%s is an unsupported format.\n", *snd_midipatchset);
		}
	}
	if (result == FMOD_OK)
	{
		SetCustomLoopPts(stream);
		return new FMODStreamCapsule(stream, this, url ? filename_or_data : NULL);
	}
	return NULL;
}

//==========================================================================
//
// FMODSoundRenderer :: StartSound
//
//==========================================================================

FISoundChannel *FMODSoundRenderer::StartSound(SoundHandle sfx, float vol, int pitch, int flags, FISoundChannel *reuse_chan)
{
	FMOD_RESULT result;
	FMOD_MODE mode;
	FMOD::Channel *chan;
	float freq;

	if (FMOD_OK == ((FMOD::Sound *)sfx.data)->getDefaults(&freq, NULL, NULL, NULL))
	{
		freq = PITCH(freq, pitch);
	}
	else
	{
		freq = 0;
	}

	GRolloff = NULL;	// Do 2D sounds need rolloff?
	result = Sys->playSound(FMOD_CHANNEL_FREE, (FMOD::Sound *)sfx.data, true, &chan);
	if (FMOD_OK == result)
	{
		result = chan->getMode(&mode);

		if (result != FMOD_OK)
		{
			assert(0);
			mode = FMOD_SOFTWARE;
		}
		mode = (mode & ~FMOD_3D) | FMOD_2D;
		if (flags & SNDF_LOOP)
		{
			mode &= ~FMOD_LOOP_OFF;
			if (!(mode & (FMOD_LOOP_NORMAL | FMOD_LOOP_BIDI)))
			{
				mode |= FMOD_LOOP_NORMAL;
			}
		}
		else
		{
			mode |= FMOD_LOOP_OFF;
		}
		chan->setMode(mode);
		chan->setChannelGroup((flags & SNDF_NOPAUSE) ? SfxGroup : PausableSfx);
		if (freq != 0)
		{
			chan->setFrequency(freq);
		}
		chan->setVolume(vol);
		if (!HandleChannelDelay(chan, reuse_chan, flags & (SNDF_ABSTIME | SNDF_LOOP), freq))
		{
			chan->stop();
			return NULL;
		}
		if (flags & SNDF_NOREVERB)
		{
			FMOD_REVERB_CHANNELPROPERTIES reverb = { 0, };
			if (FMOD_OK == chan->getReverbProperties(&reverb))
			{
				reverb.Room = -10000;
				chan->setReverbProperties(&reverb);
			}
		}
		chan->setPaused(false);
		return CommonChannelSetup(chan, reuse_chan);
	}

	//DPrintf ("Sound %s failed to play: %d\n", sfx->name.GetChars(), result);
	return NULL;
}

//==========================================================================
//
// FMODSoundRenderer :: StartSound3D
//
//==========================================================================

CVAR(Float, snd_3dspread, 180, 0)

FISoundChannel *FMODSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener *listener, float vol, 
	FRolloffInfo *rolloff, float distscale,
	int pitch, int priority, const FVector3 &pos, const FVector3 &vel,
	int channum, int flags, FISoundChannel *reuse_chan)
{
	FMOD_RESULT result;
	FMOD_MODE mode;
	FMOD::Channel *chan;
	float freq;
	float def_freq, def_vol, def_pan;
	int numchans;
	int def_priority;

	if (FMOD_OK == ((FMOD::Sound *)sfx.data)->getDefaults(&def_freq, &def_vol, &def_pan, &def_priority))
	{
		freq = PITCH(def_freq, pitch);
		// Change the sound's default priority before playing it.
		((FMOD::Sound *)sfx.data)->setDefaults(def_freq, def_vol, def_pan, clamp(def_priority - priority, 1, 256));
	}
	else
	{
		freq = 0;
		def_priority = -1;
	}

	// Play it.
	GRolloff = rolloff;
	GDistScale = distscale;

	// Experiments indicate that playSound will ignore priorities and always succeed
	// as long as the parameters are set properly. It will first try to kick out sounds
	// with the same priority level but has no problem with kicking out sounds at
	// higher priority levels if it needs to.
	result = Sys->playSound(FMOD_CHANNEL_FREE, (FMOD::Sound *)sfx.data, true, &chan);

	// Then set the priority back.
	if (def_priority >= 0)
	{
		((FMOD::Sound *)sfx.data)->setDefaults(def_freq, def_vol, def_pan, def_priority);
	}

	// Reduce volume of stereo sounds, because each channel will be summed together
	// and is likely to be very similar, resulting in an amplitude twice what it
	// would have been had it been mixed to mono.
	if (FMOD_OK == ((FMOD::Sound *)sfx.data)->getFormat(NULL, NULL, &numchans, NULL))
	{
		if (numchans > 1)
		{
			vol *= 0.5f;
		}
	}

	if (FMOD_OK == result)
	{
		result = chan->getMode(&mode);
		if (result != FMOD_OK)
		{
			mode = FMOD_3D | FMOD_SOFTWARE;
		}
		if (flags & SNDF_LOOP)
		{
			mode &= ~FMOD_LOOP_OFF;
			if (!(mode & (FMOD_LOOP_NORMAL | FMOD_LOOP_BIDI)))
			{
				mode |= FMOD_LOOP_NORMAL;
			}
		}
		else
		{
			// FMOD_LOOP_OFF overrides FMOD_LOOP_NORMAL and FMOD_LOOP_BIDI
			mode |= FMOD_LOOP_OFF;
		}
		mode = SetChanHeadSettings(listener, chan, pos, !!(flags & SNDF_AREA), mode);
		chan->setMode(mode);
		chan->setChannelGroup((flags & SNDF_NOPAUSE) ? SfxGroup : PausableSfx);

		if (freq != 0)
		{
			chan->setFrequency(freq);
		}
		chan->setVolume(vol);
		if (mode & FMOD_3D)
		{
			chan->set3DAttributes((FMOD_VECTOR *)&pos[0], (FMOD_VECTOR *)&vel[0]);
			chan->set3DSpread(snd_3dspread);
		}
		if (!HandleChannelDelay(chan, reuse_chan, flags & (SNDF_ABSTIME | SNDF_LOOP), freq))
		{
			// FMOD seems to get confused if you stop a channel right after
			// starting it, so hopefully this function will never fail.
			// (Presumably you need an update between them, but I haven't
			// tested this hypothesis.)
			chan->stop();
			return NULL;
		}
		if (flags & SNDF_NOREVERB)
		{
			FMOD_REVERB_CHANNELPROPERTIES reverb = { 0, };
			if (FMOD_OK == chan->getReverbProperties(&reverb))
			{
				reverb.Room = -10000;
				chan->setReverbProperties(&reverb);
			}
		}
		chan->setPaused(false);
		chan->getPriority(&def_priority);
		FISoundChannel *schan = CommonChannelSetup(chan, reuse_chan);
		schan->Rolloff = *rolloff;
		return schan;
	}

	GRolloff = NULL;
	//DPrintf ("Sound %s failed to play: %d\n", sfx->name.GetChars(), result);
	return 0;
}

//==========================================================================
//
// FMODSoundRenderer :: MarkStartTime
//
// Marks a channel's start time without actually playing it.
//
//==========================================================================

void FMODSoundRenderer::MarkStartTime(FISoundChannel *chan)
{
	Sys->getDSPClock(&chan->StartTime.Hi, &chan->StartTime.Lo);
}

//==========================================================================
//
// FMODSoundRenderer :: HandleChannelDelay
//
// If the sound is restarting, seek it to its proper place. Returns false
// if the sound would have ended.
//
// Otherwise, record its starting time, and return true.
//
//==========================================================================

bool FMODSoundRenderer::HandleChannelDelay(FMOD::Channel *chan, FISoundChannel *reuse_chan, int flags, float freq) const
{
	if (reuse_chan != NULL)
	{ // Sound is being restarted, so seek it to the position
	  // it would be in now if it had never been evicted.
		QWORD_UNION nowtime;
		chan->getDelay(FMOD_DELAYTYPE_DSPCLOCK_START, &nowtime.Hi, &nowtime.Lo);

		// If abstime is set, the sound is being restored, and
		// the channel's start time is actually its seek position.
		if (flags & SNDF_ABSTIME)
		{
			unsigned int seekpos = reuse_chan->StartTime.Lo;
			if (seekpos > 0)
			{
				chan->setPosition(seekpos, FMOD_TIMEUNIT_PCM);
			}
			reuse_chan->StartTime.AsOne = QWORD(nowtime.AsOne - seekpos * OutputRate / freq);
		}
		else if (reuse_chan->StartTime.AsOne != 0)
		{
			QWORD difftime = nowtime.AsOne - reuse_chan->StartTime.AsOne;
			if (difftime > 0)
			{
				// Clamp the position of looping sounds to be within the sound.
				// If we try to start it several minutes past its normal end,
				// FMOD doesn't like that.
				// FIXME: Clamp this right for loops that don't cover the whole sound.
				if (flags & SNDF_LOOP)
				{
					FMOD::Sound *sound;
					if (FMOD_OK == chan->getCurrentSound(&sound))
					{
						unsigned int len;
						if (FMOD_OK == sound->getLength(&len, FMOD_TIMEUNIT_MS) && len != 0)
						{
							difftime %= len;
						}
					}
				}
				return chan->setPosition((unsigned int)(difftime / OutputRate), FMOD_TIMEUNIT_MS) == FMOD_OK;
			}
		}
	}
	else
	{
//		chan->setDelay(FMOD_DELAYTYPE_DSPCLOCK_START, DSPClock.Hi, DSPClock.Lo);
	}
	return true;
}

//==========================================================================
//
// FMODSoundRenderer :: SetChanHeadSettings
//
// If this sound is played at the same coordinates as the listener, make
// it head relative. Also, area sounds should use no 3D panning if close
// enough to the listener.
//
//==========================================================================

FMOD_MODE FMODSoundRenderer::SetChanHeadSettings(SoundListener *listener, FMOD::Channel *chan,
												 const FVector3 &pos, bool areasound, 
												 FMOD_MODE oldmode) const
{
	if (!listener->valid)
	{
		return oldmode;
	}
	FVector3 cpos, mpos;

	cpos = listener->position;

	if (areasound)
	{
		float level, old_level;

		// How far are we from the perceived sound origin? Within a certain
		// short distance, we interpolate between 2D panning and full 3D panning.
		const double interp_range = 32.0;
		double dist_sqr = (cpos - pos).LengthSquared();

		if (dist_sqr == 0)
		{
			level = 0;
		}
		else if (dist_sqr <= interp_range * interp_range)
		{ // Within interp_range: Interpolate between none and full 3D panning.
			level = float(1 - (interp_range - sqrt(dist_sqr)) / interp_range);
		}
		else
		{ // Beyond interp_range: Normal 3D panning.
			level = 1;
		}
		if (chan->get3DPanLevel(&old_level) == FMOD_OK && old_level != level)
		{ // Only set it if it's different.
			chan->set3DPanLevel(level);
			if (level < 1)
			{ // Let the noise come from all speakers, not just the front ones.
			  // A centered 3D sound does not play at full volume, so neither should the 2D-panned one.
			  // This is sqrt(0.5), which is the result for a centered equal power panning.
				chan->setSpeakerMix(0.70711f,0.70711f,0.70711f,0.70711f,0.70711f,0.70711f,0.70711f,0.70711f);
			}
		}
		return oldmode;
	}
	else if (cpos == pos)
	{ // Head relative
		return (oldmode & ~FMOD_3D) | FMOD_2D;
	}
	// World relative
	return (oldmode & ~FMOD_2D) | FMOD_3D;
}

//==========================================================================
//
// FMODSoundRenderer :: CommonChannelSetup
//
// Assign an end callback to the channel and allocates a game channel for
// it.
//
//==========================================================================

FISoundChannel *FMODSoundRenderer::CommonChannelSetup(FMOD::Channel *chan, FISoundChannel *reuse_chan) const
{
	FISoundChannel *schan;
	
	if (reuse_chan != NULL)
	{
		schan = reuse_chan;
		schan->SysChannel = chan;
	}
	else
	{
		schan = S_GetChannel(chan);
		chan->getDelay(FMOD_DELAYTYPE_DSPCLOCK_START, &schan->StartTime.Hi, &schan->StartTime.Lo);
	}
	chan->setUserData(schan);
	chan->setCallback(ChannelCallback);
	GRolloff = NULL;
	return schan;
}

//==========================================================================
//
// FMODSoundRenderer :: StopSound
//
//==========================================================================

void FMODSoundRenderer::StopChannel(FISoundChannel *chan)
{
	if (chan != NULL && chan->SysChannel != NULL)
	{
		((FMOD::Channel *)chan->SysChannel)->stop();
	}
}

//==========================================================================
//
// FMODSoundRenderer :: GetPosition
//
// Returns position of sound on this channel, in samples.
//
//==========================================================================

unsigned int FMODSoundRenderer::GetPosition(FISoundChannel *chan)
{
	unsigned int pos;

	if (chan == NULL || chan->SysChannel == NULL)
	{
		return 0;
	}
	((FMOD::Channel *)chan->SysChannel)->getPosition(&pos, FMOD_TIMEUNIT_PCM);
	return pos;
}

//==========================================================================
//
// FMODSoundRenderer :: GetAudibility
//
// Returns the audible volume of the channel, after rollof and any other
// factors are applied.
//
//==========================================================================

float FMODSoundRenderer::GetAudibility(FISoundChannel *chan)
{
	float aud;

	if (chan == NULL || chan->SysChannel == NULL)
	{
		return 0;
	}
	((FMOD::Channel *)chan->SysChannel)->getAudibility(&aud);
	return aud;
}

//==========================================================================
//
// FMODSoundRenderer :: SetSfxPaused
//
//==========================================================================

void FMODSoundRenderer::SetSfxPaused(bool paused, int slot)
{
	int oldslots = SFXPaused;

	if (paused)
	{
		SFXPaused |= 1 << slot;
	}
	else
	{
		SFXPaused &= ~(1 << slot);
	}
	//Printf("%d\n", SFXPaused);
	if (oldslots != 0 && SFXPaused == 0)
	{
		PausableSfx->setPaused(false);
	}
	else if (oldslots == 0 && SFXPaused != 0)
	{
		PausableSfx->setPaused(true);
	}
}

//==========================================================================
//
// FMODSoundRenderer :: SetInactive
//
// This is similar to SetSfxPaused but will *pause* everything, including
// the global reverb effect. This is meant to be used only when the
// game is deactivated, not for general sound pausing.
//
//==========================================================================

void FMODSoundRenderer::SetInactive(bool inactive)
{
	if (ChannelGroupTargetUnit != NULL)
	{
		ChannelGroupTargetUnit->setActive(!inactive);
	}
}

//==========================================================================
//
// FMODSoundRenderer :: UpdateSoundParams3D
//
//==========================================================================

void FMODSoundRenderer::UpdateSoundParams3D(SoundListener *listener, FISoundChannel *chan, bool areasound, const FVector3 &pos, const FVector3 &vel)
{
	if (chan == NULL || chan->SysChannel == NULL)
		return;

	FMOD::Channel *fchan = (FMOD::Channel *)chan->SysChannel;
	FMOD_MODE oldmode, mode;

	if (fchan->getMode(&oldmode) != FMOD_OK)
	{
		oldmode = FMOD_3D | FMOD_SOFTWARE;
	}
	mode = SetChanHeadSettings(listener, fchan, pos, areasound, oldmode);
	if (mode != oldmode)
	{ // Only set the mode if it changed.
		fchan->setMode(mode);
	}
	fchan->set3DAttributes((FMOD_VECTOR *)&pos[0], (FMOD_VECTOR *)&vel[0]);
}

//==========================================================================
//
// FMODSoundRenderer :: UpdateListener
//
//==========================================================================

void FMODSoundRenderer::UpdateListener(SoundListener *listener)
{
	FMOD_VECTOR pos, vel;
	FMOD_VECTOR forward;
	FMOD_VECTOR up;

	if (!listener->valid)
	{
		return;
	}

	// Set velocity to 0 to prevent crazy doppler shifts just from running.

	vel.x = listener->velocity.X;
	vel.y = listener->velocity.Y;
	vel.z = listener->velocity.Z;
	pos.x = listener->position.X;
	pos.y = listener->position.Y;
	pos.z = listener->position.Z;

	float angle = listener->angle;
	forward.x = cos(angle);
	forward.y = 0;
	forward.z = sin(angle);

	up.x = 0;
	up.y = 1;
	up.z = 0;

	Sys->set3DListenerAttributes(0, &pos, &vel, &forward, &up);

	bool underwater = false;
	const ReverbContainer *env;

	underwater = (listener->underwater && snd_waterlp);
	if (ForcedEnvironment)
	{
		env = ForcedEnvironment;
	}
	else
	{
		env = listener->Environment;
		if (env == NULL)
		{
			env = DefaultEnvironments[0];
		}
	}
	if (env != PrevEnvironment || env->Modified)
	{
		DPrintf ("Reverb Environment %s\n", env->Name);
		const_cast<ReverbContainer*>(env)->Modified = false;
		Sys->setReverbProperties((FMOD_REVERB_PROPERTIES *)(&env->Properties));
		PrevEnvironment = env;

		if (!SfxReverbHooked)
		{
			SfxReverbHooked = ReconnectSFXReverbUnit();
		}
	}

	if (underwater || env->SoftwareWater)
	{
		//PausableSfx->setPitch(0.64171f);		// This appears to be what Duke 3D uses
		PausableSfx->setPitch(0.7937005f);		// Approx. 4 semitones lower; what Nash suggested
		if (WaterLP != NULL)
		{
			if (LastWaterLP != snd_waterlp)
			{
				LastWaterLP = snd_waterlp;
				WaterLP->setParameter(FMOD_DSP_LOWPASS_CUTOFF, snd_waterlp);
			}
			WaterLP->setActive(true);
			if (WaterReverb != NULL && snd_waterreverb)
			{
				WaterReverb->setActive(true);
				WaterReverb->setBypass(false);
				SfxConnection->setMix(0);
			}
			else
			{
				// Let some of the original mix through so that high frequencies are
				// not completely lost. The reverb unit has its own connection and
				// preserves dry sounds itself if used.
				SfxConnection->setMix(0.1f);
				if (WaterReverb != NULL)
				{
					WaterReverb->setActive(true);
					WaterReverb->setBypass(true);
				}
			}
		}
	}
	else
	{
		PausableSfx->setPitch(1);
		if (WaterLP != NULL)
		{
			SfxConnection->setMix(1);
			WaterLP->setActive(false);
			if (WaterReverb != NULL)
			{
				WaterReverb->setActive(false);
			}
		}
	}
}

//==========================================================================
//
// FMODSoundRenderer :: ReconnectSFXReverbUnit
//
// Locates the DSP unit responsible for software 3D reverb. There is only
// one, and it by default is connected directly to the ChannelGroup Target
// Unit. Older versions of FMOD created this at startup; newer versions
// delay creating it until the first call to setReverbProperties, at which
// point it persists until the system is closed.
//
// Upon locating the proper DSP unit, reconnects it to serve as an input to
// our water DSP chain after the Pausable SFX ChannelGroup.
//
//==========================================================================

bool FMODSoundRenderer::ReconnectSFXReverbUnit()
{
	FMOD::DSP *unit;
	FMOD_DSP_TYPE type;
	int numinputs, i;

	if (ChannelGroupTargetUnit == NULL || SfxReverbPlaceholder == NULL)
	{
		return false;
	}
	// Look for SFX Reverb unit
	if (FMOD_OK != ChannelGroupTargetUnit->getNumInputs(&numinputs))
	{
		return false;
	}
	for (i = numinputs - 1; i >= 0; --i)
	{
		if (FMOD_OK == ChannelGroupTargetUnit->getInput(i, &unit, NULL) &&
			FMOD_OK == unit->getType(&type))
		{
			if (type == FMOD_DSP_TYPE_SFXREVERB)
			{
				break;
			}
		}
	}
	if (i < 0)
	{
		return false;
	}

	// Found it! Now move it in the DSP graph to be done before the water
	// effect.
	if (FMOD_OK != ChannelGroupTargetUnit->disconnectFrom(unit))
	{
		return false;
	}
	if (FMOD_OK != SfxReverbPlaceholder->addInput(unit, NULL))
	{
		return false;
	}
	return true;
}

//==========================================================================
//
// FMODSoundRenderer :: Sync
//
// Used by the save/load code to restart sounds at the same position they
// were in at the time of saving. Must not be nested.
//
//==========================================================================

void FMODSoundRenderer::Sync(bool sync)
{
	DSPLocked = sync;
	if (sync)
	{
		Sys->lockDSP();
		Sys->getDSPClock(&DSPClock.Hi, &DSPClock.Lo);
	}
	else
	{
		Sys->unlockDSP();
	}
}

//==========================================================================
//
// FMODSoundRenderer :: UpdateSounds
//
//==========================================================================

void FMODSoundRenderer::UpdateSounds()
{
	// Any sounds played between now and the next call to this function
	// will start exactly one tic from now.
	Sys->getDSPClock(&DSPClock.Hi, &DSPClock.Lo);
	DSPClock.AsOne += OutputRate / TICRATE;
	Sys->update();
}

//==========================================================================
//
// FMODSoundRenderer :: LoadSoundRaw
//
//==========================================================================

SoundHandle FMODSoundRenderer::LoadSoundRaw(BYTE *sfxdata, int length, int frequency, int channels, int bits, int loopstart)
{
	FMOD_CREATESOUNDEXINFO exinfo;
	SoundHandle retval = { NULL };
	int numsamples;

	if (length <= 0)
	{
		return retval;
	}

	InitCreateSoundExInfo(&exinfo);
	exinfo.length = length;
	exinfo.numchannels = channels;
	exinfo.defaultfrequency = frequency;
	switch (bits)
	{
	case 8:
		// Need to convert sample data from unsigned to signed.
		for (int i = 0; i < length; ++i)
		{
			sfxdata[i] = sfxdata[i] - 128;
		}

	case -8:
		exinfo.format = FMOD_SOUND_FORMAT_PCM8;
		numsamples = length;
		break;

	case 16:
		exinfo.format = FMOD_SOUND_FORMAT_PCM16;
		numsamples = length >> 1;
		break;

	case 32:
		exinfo.format = FMOD_SOUND_FORMAT_PCM32;
		numsamples = length >> 2;
		break;

	default:
		return retval;
	}

	const FMOD_MODE samplemode = FMOD_3D | FMOD_OPENMEMORY | FMOD_SOFTWARE | FMOD_OPENRAW;
	FMOD::Sound *sample;
	FMOD_RESULT result;

	result = Sys->createSound((char *)sfxdata, samplemode, &exinfo, &sample);
	if (result != FMOD_OK)
	{
		DPrintf("Failed to allocate sample: Error %d\n", result);
		return retval;
	}

	if (loopstart >= 0)
	{
		sample->setLoopPoints(loopstart, FMOD_TIMEUNIT_PCM, numsamples - 1, FMOD_TIMEUNIT_PCM);
	}

	retval.data = sample;
	return retval;
}

//==========================================================================
//
// FMODSoundRenderer :: LoadSound
//
//==========================================================================

SoundHandle FMODSoundRenderer::LoadSound(BYTE *sfxdata, int length)
{
	FMOD_CREATESOUNDEXINFO exinfo;
	SoundHandle retval = { NULL };

	if (length == 0) return retval;

	InitCreateSoundExInfo(&exinfo);
	exinfo.length = length;

	const FMOD_MODE samplemode = FMOD_3D | FMOD_OPENMEMORY | FMOD_SOFTWARE;
	FMOD::Sound *sample;
	FMOD_RESULT result;

	result = Sys->createSound((char *)sfxdata, samplemode, &exinfo, &sample);
	if (result != FMOD_OK)
	{
		DPrintf("Failed to allocate sample: Error %d\n", result);
		return retval;
	}
	SetCustomLoopPts(sample);
	retval.data = sample;
	return retval;
}

//==========================================================================
//
// FMODSoundRenderer :: UnloadSound
//
//==========================================================================

void FMODSoundRenderer::UnloadSound(SoundHandle sfx)
{
	if (sfx.data != NULL)
	{
		((FMOD::Sound *)sfx.data)->release();
	}
}

//==========================================================================
//
// FMODSoundRenderer :: GetMSLength
//
//==========================================================================

unsigned int FMODSoundRenderer::GetMSLength(SoundHandle sfx)
{
	if (sfx.data != NULL)
	{
		unsigned int length;

		if (((FMOD::Sound *)sfx.data)->getLength(&length, FMOD_TIMEUNIT_MS) == FMOD_OK)
		{
			return length;
		}
	}
	return 0;	// Don't know.
}


//==========================================================================
//
// FMODSoundRenderer :: GetMSLength
//
//==========================================================================

unsigned int FMODSoundRenderer::GetSampleLength(SoundHandle sfx)
{
	if (sfx.data != NULL)
	{
		unsigned int length;

		if (((FMOD::Sound *)sfx.data)->getLength(&length, FMOD_TIMEUNIT_PCM) == FMOD_OK)
		{
			return length;
		}
	}
	return 0;	// Don't know.
}


//==========================================================================
//
// FMODSoundRenderer :: ChannelCallback								static
//
// Handles when a channel finishes playing. This is only called when
// System::update is called and is therefore asynchronous with the actual
// end of the channel.
//
//==========================================================================

FMOD_RESULT F_CALLBACK FMODSoundRenderer::ChannelCallback
	(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type, void *data1, void *data2)
{
	// [BB] This seems to solve player class related Hexen startup crashes.
	if ( channel == NULL )
		return FMOD_OK;

	FMOD::Channel *chan = (FMOD::Channel *)channel;
	FISoundChannel *schan;

	if (chan->getUserData((void **)&schan) == FMOD_OK && schan != NULL)
	{
		if (type == FMOD_CHANNEL_CALLBACKTYPE_END)
		{
			S_ChannelEnded(schan);
		}
		else if (type == FMOD_CHANNEL_CALLBACKTYPE_VIRTUALVOICE)
		{
			S_ChannelVirtualChanged(schan, data1 != 0);
		}
	}
	return FMOD_OK;
}


//==========================================================================
//
// FMODSoundRenderer :: RolloffCallback								static
//
// Calculates a volume for the sound based on distance.
//
//==========================================================================

float F_CALLBACK FMODSoundRenderer::RolloffCallback(FMOD_CHANNEL *channel, float distance)
{
	FMOD::Channel *chan = (FMOD::Channel *)channel;
	FISoundChannel *schan;

	if (GRolloff != NULL)
	{
		return S_GetRolloff(GRolloff, distance * GDistScale, true);
	}
	else if (chan->getUserData((void **)&schan) == FMOD_OK && schan != NULL)
	{
		return S_GetRolloff(&schan->Rolloff, distance * schan->DistanceScale, true);
	}
	else
	{
		return 0;
	}
}

//==========================================================================
//
// FMODSoundRenderer :: DrawWaveDebug
//
// Bit 0: ( 1) Show oscilloscope for sfx.
// Bit 1: ( 2) Show spectrum for sfx.
// Bit 2: ( 4) Show oscilloscope for music.
// Bit 3: ( 8) Show spectrum for music.
// Bit 4: (16) Show oscilloscope for all sounds.
// Bit 5: (32) Show spectrum for all sounds.
//
//==========================================================================

void FMODSoundRenderer::DrawWaveDebug(int mode)
{
	const int window_height = 100;
	int window_size;
	int numoutchans;
	int y;

	if (FMOD_OK != Sys->getSoftwareFormat(NULL, NULL, &numoutchans, NULL, NULL, NULL))
	{
		return;
	}

	// Scale all the channel windows so one group fits completely on one row, with
	// 16 pixels of padding between each window.
	window_size = (screen->GetWidth() - 16) / numoutchans - 16;

	float *wavearray = (float*)alloca(window_size*sizeof(float));

	y = 16;
	y = DrawChannelGroupOutput(SfxGroup, wavearray, window_size, window_height, y, mode);
	y = DrawChannelGroupOutput(MusicGroup, wavearray, window_size, window_height, y, mode >> 2);
	y = DrawSystemOutput(wavearray, window_size, window_height, y, mode >> 4);
}

//==========================================================================
//
// FMODSoundRenderer :: DrawChannelGroupOutput
//
// Draws an oscilloscope and/or a spectrum for a channel group.
//
//==========================================================================

int FMODSoundRenderer::DrawChannelGroupOutput(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, int mode)
{
	int y1, y2;

	switch (mode & 0x03)
	{
	case 0x01:		// Oscilloscope only
		return DrawChannelGroupWaveData(group, wavearray, width, height, y, false);

	case 0x02:		// Spectrum only
		return DrawChannelGroupSpectrum(group, wavearray, width, height, y, false);

	case 0x03:		// Oscilloscope + Spectrum
		width = (width + 16) / 2 - 16;
		y1 = DrawChannelGroupSpectrum(group, wavearray, width, height, y, true);
		y2 = DrawChannelGroupWaveData(group, wavearray, width, height, y, true);
		return MAX(y1, y2);
	}
	return y;
}

//==========================================================================
//
// FMODSoundRenderer :: DrawSystemOutput
//
// Like DrawChannelGroupOutput(), but uses the system object.
//
//==========================================================================

int FMODSoundRenderer::DrawSystemOutput(float *wavearray, int width, int height, int y, int mode)
{
	int y1, y2;

	switch (mode & 0x03)
	{
	case 0x01:		// Oscilloscope only
		return DrawSystemWaveData(wavearray, width, height, y, false);

	case 0x02:		// Spectrum only
		return DrawSystemSpectrum(wavearray, width, height, y, false);

	case 0x03:		// Oscilloscope + Spectrum
		width = (width + 16) / 2 - 16;
		y1 = DrawSystemSpectrum(wavearray, width, height, y, true);
		y2 = DrawSystemWaveData(wavearray, width, height, y, true);
		return MAX(y1, y2);
	}
	return y;
}

//==========================================================================
//
// FMODSoundRenderer :: DrawChannelGroupWaveData
//
// Draws all the output channels for a specified channel group.
// Setting skip to true causes it to skip every other window.
//
//==========================================================================

int FMODSoundRenderer::DrawChannelGroupWaveData(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip)
{
	int drawn = 0;
	int x = 16;

	while (FMOD_OK == group->getWaveData(wavearray, width, drawn))
	{
		drawn++;
		DrawWave(wavearray, x, y, width, height);
		x += (width + 16) << int(skip);
	}
	if (drawn)
	{
		y += height + 16;
	}
	return y;
}

//==========================================================================
//
// FMODSoundRenderer::DrawSystemWaveData
//
// Like DrawChannelGroupWaveData, but it uses the system object to get the
// complete output.
//
//==========================================================================

int FMODSoundRenderer::DrawSystemWaveData(float *wavearray, int width, int height, int y, bool skip)
{
	int drawn = 0;
	int x = 16;

	while (FMOD_OK == Sys->getWaveData(wavearray, width, drawn))
	{
		drawn++;
		DrawWave(wavearray, x, y, width, height);
		x += (width + 16) << int(skip);
	}
	if (drawn)
	{
		y += height + 16;
	}
	return y;
}

//==========================================================================
//
// FMODSoundRenderer :: DrawWave
//
// Draws an oscilloscope at the specified coordinates on the screen. Each
// entry in the wavearray buffer has its own column. IOW, there are <width>
// entries in wavearray.
//
//==========================================================================

void FMODSoundRenderer::DrawWave(float *wavearray, int x, int y, int width, int height)
{
	float scale = height / 2.f;
	float mid = y + scale;
	int i;

	// Draw a box around the oscilloscope.
	screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200));
	screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200));
	screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200));
	screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200));

	// Draw the actual oscilloscope.
	if (screen->Accel2D)
	{ // Drawing this with lines is super-slow without hardware acceleration, at least with
	  // the debug build.
		float lasty = mid - wavearray[0] * scale;
		float newy;
		for (i = 1; i < width; ++i)
		{
			newy = mid - wavearray[i] * scale;
			screen->DrawLine(x + i - 1, int(lasty), x + i, int(newy), -1, MAKEARGB(255,255,248,248));
			lasty = newy;
		}
	}
	else
	{
		for (i = 0; i < width; ++i)
		{
			float y = wavearray[i] * scale + mid;
			screen->DrawPixel(x + i, int(y), -1, MAKEARGB(255,255,255,255));
		}
	}
}

//==========================================================================
//
// FMODSoundRenderer :: DrawChannelGroupSpectrum
//
// Draws all the spectrum for a specified channel group.
// Setting skip to true causes it to skip every other window, starting at
// the second one.
//
//==========================================================================

int FMODSoundRenderer::DrawChannelGroupSpectrum(FMOD::ChannelGroup *group, float *spectrumarray, int width, int height, int y, bool skip)
{
	int drawn = 0;
	int x = 16;

	if (skip)
	{
		x += width + 16;
	}
	while (FMOD_OK == group->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE))
	{
		drawn++;
		DrawSpectrum(spectrumarray, x, y, width, height);
		x += (width + 16) << int(skip);
	}
	if (drawn)
	{
		y += height + 16;
	}
	return y;
}

//==========================================================================
//
// FMODSoundRenderer::DrawSystemSpectrum
//
// Like DrawChannelGroupSpectrum, but it uses the system object to get the
// complete output.
//
//==========================================================================

int FMODSoundRenderer::DrawSystemSpectrum(float *spectrumarray, int width, int height, int y, bool skip)
{
	int drawn = 0;
	int x = 16;

	if (skip)
	{
		x += width + 16;
	}
	while (FMOD_OK == Sys->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE))
	{
		drawn++;
		DrawSpectrum(spectrumarray, x, y, width, height);
		x += (width + 16) << int(skip);
	}
	if (drawn)
	{
		y += height + 16;
	}
	return y;
}

//==========================================================================
//
// FMODSoundRenderer :: DrawSpectrum
//
// Draws a spectrum at the specified coordinates on the screen.
//
//==========================================================================

void FMODSoundRenderer::DrawSpectrum(float *spectrumarray, int x, int y, int width, int height)
{
	float scale = height / 2.f;
	float mid = y + scale;
	float db;
	int top;

	// Draw a border and dark background for the spectrum.
	screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200));
	screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200));
	screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200));
	screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200));
	screen->Dim(MAKERGB(0,0,0), 0.3f, x, y, width, height);

	// Draw the actual spectrum.
	for (int i = 0; i < width; ++i)
	{
		db = spectrumarray[i * (SPECTRUM_SIZE - 2) / width + 1];
		db = MAX(-150.f, 10 * log10f(db) * 2);		// Convert to decibels and clamp
		db = 1.f - (db / -150.f);
		db *= height;
		top = (int)db;
		if (top >= height)
		{
			top = height - 1;
		}
//		screen->Clear(x + i, int(y + height - db), x + i + 1, y + height, -1, MAKEARGB(255, 255, 255, 40));
		screen->Dim(MAKERGB(255,255,40), 0.65f, x + i, y + height - top, 1, top);
	}
}

//==========================================================================
//
// FMODSoundRenderer :: DecodeSample
//
// Uses FMOD to decode a compressed sample to a 16-bit buffer. This is used
// by the DUMB XM reader to handle FMOD's OggMods.
//
//==========================================================================

short *FMODSoundRenderer::DecodeSample(int outlen, const void *coded, int sizebytes, ECodecType type)
{
	FMOD_CREATESOUNDEXINFO exinfo;
	FMOD::Sound *sound;
	FMOD_SOUND_FORMAT format;
	int channels;
	unsigned int len, amt_read;
	FMOD_RESULT result;
	short *outbuf;

	InitCreateSoundExInfo(&exinfo);
	if (type == CODEC_Vorbis)
	{
		exinfo.suggestedsoundtype = FMOD_SOUND_TYPE_OGGVORBIS;
	}
	exinfo.length = sizebytes;
	result = Sys->createSound((const char *)coded,
		FMOD_2D | FMOD_SOFTWARE | FMOD_CREATESTREAM |
		FMOD_OPENMEMORY_POINT | FMOD_OPENONLY | FMOD_LOWMEM,
		&exinfo, &sound);
	if (result != FMOD_OK)
	{
		return NULL;
	}
	result = sound->getFormat(NULL, &format, &channels, NULL);
	// TODO: Handle more formats if it proves necessary.
	if (result != FMOD_OK || format != FMOD_SOUND_FORMAT_PCM16 || channels != 1)
	{
		sound->release();
		return NULL;
	}
	len = outlen;
	// Must be malloc'ed for DUMB, which is C.
	outbuf = (short *)malloc(len);
	result = sound->readData(outbuf, len, &amt_read);
	sound->release();
	if (result == FMOD_ERR_FILE_EOF)
	{
		memset((BYTE *)outbuf + amt_read, 0, len - amt_read);
	}
	else if (result != FMOD_OK || amt_read != len)
	{
		free(outbuf);
		return NULL;
	}
	return outbuf;
}

//==========================================================================
//
// FMODSoundRenderer :: InitCreateSoundExInfo
//
// Allow for compiling with 4.26 APIs while still running with older DLLs.
//
//==========================================================================

void FMODSoundRenderer::InitCreateSoundExInfo(FMOD_CREATESOUNDEXINFO *exinfo) const
{
#if FMOD_VERSION >= 0x42600
	if (ActiveFMODVersion < 0x42600)
	{
		// This parameter was added for 4.26.00, and trying to pass it to older
		// DLLs will fail.
		exinfo->cbsize = myoffsetof(FMOD_CREATESOUNDEXINFO, ignoresetfilesystem);
	}
	else
#endif
	{
		exinfo->cbsize = sizeof(*exinfo);
	}
	memset((BYTE *)exinfo + sizeof(exinfo->cbsize), 0, exinfo->cbsize - sizeof(exinfo->cbsize));
}
#endif //NO_SOUND
